<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
    />
    <meta
      name="description"
      content="Load a photogrammetry dataset with feature ID textures from EXT_mesh_features."
    />
    <meta name="cesium-sandcastle-labels" content="3D Tiles Next" />
    <title>Cesium Demo</title>
    <script type="text/javascript" src="../Sandcastle-header.js"></script>
    <script
      type="text/javascript"
      src="../../../Build/CesiumUnminified/Cesium.js"
      nomodule
    ></script>
    <script type="module" src="../load-cesium-es6.js"></script>
  </head>
  <body
    class="sandcastle-loading"
    data-sandcastle-bucket="bucket-requirejs.html"
  >
    <style>
      @import url(../templates/bucket.css);
    </style>
    <div id="cesiumContainer" class="fullSize"></div>
    <div id="loadingOverlay"><h1>Loading...</h1></div>
    <div id="toolbar"></div>
    <script id="cesium_sandcastle_script">
      function startup(Cesium) {
        "use strict";
        //Sandcastle_Begin
        // San Francisco Ferry Building photogrammetry model provided by Aerometrex

        const viewer = new Cesium.Viewer("cesiumContainer", {
          terrainProvider: Cesium.createWorldTerrain(),
          infoBox: false,
          orderIndependentTranslucency: false,
        });

        viewer.clock.currentTime = Cesium.JulianDate.fromIso8601(
          "2021-11-09T20:27:37.016064475348684937Z"
        );

        const scene = viewer.scene;

        const tileset = new Cesium.Cesium3DTileset({
          url: Cesium.IonResource.fromAssetId(775877),
        });

        const translation = new Cesium.Cartesian3(
          -1.398521324920626,
          0.7823052871729486,
          0.7015244410592609
        );
        tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation);

        tileset.maximumScreenSpaceError = 8.0;
        scene.pickTranslucentDepth = true;
        scene.light.intensity = 7.0;

        viewer.scene.primitives.add(tileset);
        viewer.zoomTo(tileset);

        // Fly to a nice overview of the city.
        viewer.camera.flyTo({
          destination: new Cesium.Cartesian3(
            -2703640.80485846,
            -4261161.990345464,
            3887439.511104276
          ),
          orientation: new Cesium.HeadingPitchRoll(
            0.22426651143535548,
            -0.2624145362506527,
            0.000006972977223185239
          ),
        });

        // Styles =============================================================================

        const classificationStyle = new Cesium.Cesium3DTileStyle({
          color: "color(${color})",
        });

        // Shaders ============================================================================

        // Dummy shader that sets the UNLIT lighting mode. For use with the classification style
        const emptyFragmentShader =
          "void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {}";
        const unlitShader = new Cesium.CustomShader({
          lightingModel: Cesium.LightingModel.UNLIT,
          fragmentShaderText: emptyFragmentShader,
        });

        const translucentWindowsShader = new Cesium.CustomShader({
          lightingModel: Cesium.LightingModel.UNLIT,
          isTranslucent: true,
          fragmentShaderText: `
            const int WINDOW = 0;
            const int SKYLIGHT = 4;
            const int TOTAL_FEATURES = 12;
            
            void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
              int featureId = fsInput.featureIds.featureId_0;
            
              if (featureId == WINDOW || featureId == SKYLIGHT) {
                material.alpha = 0.4;
                material.roughness = 0.1;
              }
            }
            `,
        });

        const materialShader = new Cesium.CustomShader({
          lightingModel: Cesium.LightingModel.PBR,
          isTranslucent: true,
          fragmentShaderText: `
            const int WINDOW = 0;
            const int FRAME = 1;
            const int WALL = 2;
            const int ROOF = 3;
            const int SKYLIGHT = 4;
            const int AIR_CONDITIONER_WHITE = 5;
            const int AIR_CONDITIONER_BLACK = 6;
            const int AIR_CONDITIONER_TALL = 7;
            const int CLOCK = 8;
            const int PILLARS = 9;
            const int STREET_LIGHT = 10;
            const int TRAFFIC_LIGHT = 11;
            
            void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
              int featureId = fsInput.featureIds.featureId_0;
            
              if (featureId == CLOCK) {
                // Shiny brass
                material.specular = vec3(0.98, 0.90, 0.59);
                material.roughness = 0.1;
              } else if (
                featureId == STREET_LIGHT ||
                featureId == AIR_CONDITIONER_BLACK ||
                featureId == AIR_CONDITIONER_WHITE ||
                featureId == AIR_CONDITIONER_TALL ||
                featureId == ROOF
              ) {
                // dull aluminum
                material.specular = vec3(0.91, 0.92, 0.92);
                material.roughness = 0.5;
              } else if (featureId == WINDOW || featureId == SKYLIGHT) {
                // make translucent, but also set an orange emissive color so it looks like
                // it's lit from inside
                material.emissive = vec3(1.0, 0.3, 0.0);
                material.alpha = 0.5;
              } else if (featureId == WALL || featureId == FRAME || featureId == PILLARS) {
                // paint the walls and pillars white to contrast the brass clock
                material.diffuse = mix(material.diffuse, vec3(1.0), 0.8);
                material.roughness = 0.9;
              } else {
                // brighten everything else
                material.diffuse += 0.05;
                material.roughness = 0.9;
              }
            }
            `,
        });

        const NOTHING_SELECTED = 12;
        const selectFeatureShader = new Cesium.CustomShader({
          uniforms: {
            u_selectedFeature: {
              type: Cesium.UniformType.INT,
              value: NOTHING_SELECTED,
            },
          },
          lightingModel: Cesium.LightingModel.PBR,
          fragmentShaderText: `
            const int NOTHING_SELECTED = 12;
            void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
              int featureId = fsInput.featureIds.featureId_0;
            
              if (u_selectedFeature < NOTHING_SELECTED && featureId == u_selectedFeature) {
                material.specular = vec3(1.00, 0.85, 0.57);
                material.roughness = 0.1;
              }
            }
            `,
        });

        const multipleFeatureIdsShader = new Cesium.CustomShader({
          uniforms: {
            u_selectedFeature: {
              type: Cesium.UniformType.FLOAT,
              value: NOTHING_SELECTED,
            },
          },
          lightingModel: Cesium.LightingModel.UNLIT,
          fragmentShaderText: `
            const int IDS0_WINDOW = 0;
            const int IDS1_FACADE = 2;
            const int IDS1_ROOF = 3;
            const vec3 PURPLE = vec3(0.5, 0.0, 1.0);
            const vec3 YELLOW = vec3(1.0, 1.0, 0.0);
            const vec3 NO_TINT = vec3(1.0);
            
            void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
              int featureId0 = fsInput.featureIds.featureId_0; // fine features
              int featureId1 = fsInput.featureIds.featureId_1; // coarse features
            
              // use both feature ID sets to determine where the features are
              float isWindow = float(featureId0 == IDS0_WINDOW);
              float isFacade = float(featureId1 == IDS1_FACADE);
              float isRoof = float(featureId1 == IDS1_ROOF);
            
              // Tint the roof windows yellow and facade windows purple
              vec3 tint = NO_TINT;
              tint = mix(tint, YELLOW, isWindow * isRoof);
              tint = mix(tint, PURPLE, isWindow * isFacade);
              material.diffuse *= tint;
            }
            `,
        });

        // Demo Functions =====================================================================

        function defaults() {
          tileset.style = undefined;
          tileset.customShader = unlitShader;
          tileset.colorBlendMode = Cesium.Cesium3DTileColorBlendMode.HIGHLIGHT;
          tileset.colorBlendAmount = 0.5;
          tileset.featureIdLabel = 0;
        }

        const showPhotogrammetry = defaults;

        function showClassification() {
          defaults();
          tileset.style = classificationStyle;
          tileset.colorBlendMode = Cesium.Cesium3DTileColorBlendMode.MIX;
        }

        function showAlternativeClassification() {
          showClassification();
          // This dataset has a second feature ID texture.
          tileset.featureIdLabel = 1;
        }

        function translucentWindows() {
          defaults();
          tileset.customShader = translucentWindowsShader;
        }

        function pbrMaterials() {
          defaults();
          tileset.customShader = materialShader;
        }

        function goldenTouch() {
          defaults();
          tileset.customShader = selectFeatureShader;
        }

        function multipleFeatureIds() {
          defaults();
          tileset.customShader = multipleFeatureIdsShader;
        }

        // Pick Handlers ======================================================================

        // HTML overlay for showing feature name on mouseover
        const nameOverlay = document.createElement("div");
        viewer.container.appendChild(nameOverlay);
        nameOverlay.className = "backdrop";
        nameOverlay.style.display = "none";
        nameOverlay.style.position = "absolute";
        nameOverlay.style.bottom = "0";
        nameOverlay.style.left = "0";
        nameOverlay.style["pointer-events"] = "none";
        nameOverlay.style.padding = "4px";
        nameOverlay.style.backgroundColor = "black";
        nameOverlay.style.whiteSpace = "pre-line";
        nameOverlay.style.fontSize = "12px";

        let enablePicking = true;
        const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
        handler.setInputAction(function (movement) {
          if (enablePicking) {
            const pickedObject = viewer.scene.pick(movement.endPosition);
            if (pickedObject instanceof Cesium.Cesium3DTileFeature) {
              nameOverlay.style.display = "block";
              nameOverlay.style.bottom = `${
                viewer.canvas.clientHeight - movement.endPosition.y
              }px`;
              nameOverlay.style.left = `${movement.endPosition.x}px`;
              const component = pickedObject.getProperty("component");
              const message = `Component: ${component}\nFeature ID: ${pickedObject.featureId}`;
              nameOverlay.textContent = message;
            } else {
              nameOverlay.style.display = "none";
            }
          } else {
            nameOverlay.style.display = "none";
          }
        }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

        const clickHandler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
        handler.setInputAction(function (movement) {
          if (enablePicking) {
            const pickedObject = scene.pick(movement.position);
            if (
              Cesium.defined(pickedObject) &&
              Cesium.defined(pickedObject.featureId)
            ) {
              selectFeatureShader.setUniform(
                "u_selectedFeature",
                pickedObject.featureId
              );
            } else {
              selectFeatureShader.setUniform(
                "u_selectedFeature",
                NOTHING_SELECTED
              );
            }
          }
        }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

        // UI ============================================================================

        Sandcastle.addToggleButton("Enable picking", enablePicking, function (
          checked
        ) {
          enablePicking = checked;
        });

        const demos = [
          {
            text: "Photogrammetry",
            onselect: showPhotogrammetry,
          },
          {
            text: "Show Classification",
            onselect: showClassification,
          },
          {
            text: "Show Alternative Classification",
            onselect: showAlternativeClassification,
          },
          {
            text: "Translucent Windows",
            onselect: translucentWindows,
          },
          {
            text: "Stylized PBR Materials",
            onselect: pbrMaterials,
          },
          {
            text: "Golden Touch",
            onselect: goldenTouch,
          },
          {
            text: "Multiple Feature ID Sets",
            onselect: multipleFeatureIds,
          },
        ];
        Sandcastle.addToolbarMenu(demos);

        //Sandcastle_End
        Sandcastle.finishedLoading();
      }
      if (typeof Cesium !== "undefined") {
        window.startupCalled = true;
        startup(Cesium);
      }
    </script>
  </body>
</html>
